#pragma once

#include <cstdio>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>

#include <rapidjson/document.h>

#include "coin2/exchange/base/order/order.h"
#include "coin2/exchange/deribit_base/api_order/fix/fix_fields.h"
#include "coin2/exchange/deribit_base/api_util/rate_tracker.h"
#include "coin2/exchange/deribit_futures/symbology/currency.h"
#include "coin2/exchange/deribit_futures/symbology/product.h"
#include "naviasraw/fix/fixmsg.h"

namespace coin2::exchange::deribit_futures::order_v2 {

using namespace naviasraw::fix;

namespace resp_security_list {

#define FIX_PARSER_SECURITY_LIST_TAG_TRAITS(TMPL) \
  TMPL(tag::common::SecurityRequestResult)        \
  TMPL(tag::common::NoRelatedSym, int)
DEF_RESP_PARSER(SecurityListParser, FIX_PARSER_SECURITY_LIST_TAG_TRAITS);

}  // namespace resp_security_list

namespace rpt_grp_related_sym {

#define FIX_RPT_GRP_RELATED_SYMB_TAG_TRAITS(TMPL) \
  TMPL(tag::common::Symbol)                       \
  TMPL(tag::common::ContractMultiplier, int)

DEF_RESP_PARSER(RelatedSymParser, FIX_RPT_GRP_RELATED_SYMB_TAG_TRAITS);

}  // namespace rpt_grp_related_sym

namespace resp_parser {

#define FIX_PARSER_TAG_TRAITS(TMPL)     \
  TMPL(tag::common::MsgType)            \
  TMPL(tag::common::MsgSeqNum, int64_t) \
  TMPL(tag::common::ClOrdID)            \
  TMPL(tag::common::OrigClOrdID)        \
  TMPL(tag::common::Symbol)             \
  TMPL(tag::common::Text)               \
  TMPL(tag::common::ExecType, char)     \
  TMPL(tag::common::OrdStatus, char)    \
  TMPL(tag::common::Side, char)         \
  TMPL(tag::common::LastPx, double)     \
  TMPL(tag::common::Price, double)      \
  TMPL(tag::common::OrderQty, double)   \
  TMPL(tag::common::LastQty, double)

DEF_RESP_PARSER(RespParserBase, FIX_PARSER_TAG_TRAITS);

class RespParser : public RespParserBase<RespParser> {
 public:
  using Base = RespParserBase<RespParser>;
  friend Base::Base;

  template <class Tag>
  void DoProcessField(const Tag&) {
    if constexpr (std::is_same_v<Tag, tag::common::MsgType>) {
      if (this->GetVal(tag::common::MsgType()).size() == 1) {
        switch (this->GetVal(tag::common::MsgType())[0]) {
          case '8':
            this->SetExpectedTags(
                tag::common::ClOrdID(),
                tag::common::OrigClOrdID(),
                tag::common::OrdStatus(),
                tag::common::Side(),
                tag::common::OrderQty(),
                tag::common::Price(),
                tag::common::Text(),
                tag::common::Symbol());
            break;
          case '9':
            this->SetExpectedTags(
                tag::common::ClOrdID(),
                tag::common::OrigClOrdID(),
                tag::common::OrdStatus(),
                tag::common::Text());
            break;
          default:
            break;
        }
      }
    }
  }
};

}  // namespace resp_parser

class DeribitFuturesParser2 : public base::order::BaseParser {
 public:
  DeribitFuturesParser2();
  const ParseResult& ParseRawMessage(const RawMessage& msg) override;
  AccountBalance ParseBalance(const JsonValue& doc) override;
  AccountPosition ParsePosition(const JsonValue& doc) override;

 private:
  void ParseListenKey();
  void ParseSubmitResponse();
  void ParseCancelResponse();
  void ParseRestAuthToken();
  std::string ParseAuthToken(const JsonValue& doc);

  void ParseRestAccounts();
  void ParseRestFillInfoList();
  void ParseRestAccountBalance();
  void ParseRestAccountPosition();
  void ParseWsMessage();

  void ParseDeposits(const std::string& query_currency);
  void ParseWithdrawals(const std::string& query_currency);
  void ParseTransfers(const std::string& query_currency);

  // Handle FIX message.
  void ParseFixMessage2();
  void ParseFixSubmitResponse2(resp_parser::RespParser& resp_parser);
  void ParseFixCancelResponse2(resp_parser::RespParser& resp_parser);
  void ParseFixOrderInfo2(resp_parser::RespParser& resp_parser);
  void ParseFixSecurityList2(std::string_view message);
  void ParseRestOrderInfoList();
  bool ParseWsPositionFromChanges(const JsonValue& doc, AccountPosition* acc_position);
  bool ParseWsOrderFromChanges(const JsonValue& doc, ProductOrderBundle* product_order_bundle);
  bool ParseWsTradeFromChanges(const JsonValue& doc, ProductFillBundle* product_fill_bundle);
  auto ParseRestOrderInfo(const JsonValue& data) -> coin::proto::ProductOrderElement;
  auto GetContractValue(const std::string& symbol) -> double;
  void SetContractValue(const std::string& symbol, double val);
  bool CheckIfInverseSymbol(const std::string& native_symbol);

 private:
  bool is_balance_initialized_{false};
  bool is_position_initialized_{false};
  std::unordered_map<std::string, coin::proto::CurrencyBalance> balance_map_;
  std::unordered_map<std::string, coin::proto::ProductPosition> position_map_;
  std::unordered_map<std::string, coin::proto::ProductPosition> undetected_position_map_;
  std::unordered_map<std::string, double> contract_value_map_;
  std::string currency_base_;

  std::unordered_map<int, std::unordered_set<std::string>> transfer_initialized_set_;
  std::unordered_map<int, std::unordered_map<std::string, std::string>> transfer_record_map_;

  std::unordered_map<std::string, bool> contract_inverse_map_;

  resp_parser::RespParser resp_parser_;
};

}  // namespace coin2::exchange::deribit_futures::order_v2
